package com.jintian.smart.kernel.module.impl;

import lombok.extern.slf4j.Slf4j;

import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketPermission;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLStreamHandlerFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

@Slf4j
public class SmartUrlClassLoader extends SecureClassLoader {
    private final List<URL> urls;
    // private AccessControlContext acc = null;

    public SmartUrlClassLoader(URL[] urls, ClassLoader parent) {
        super(parent);
        // SecurityManager securityManager = System.getSecurityManager();
        // if (securityManager != null) {
        //  // 可以选择保留或移除检查
        // }
        this.urls = new ArrayList<>(Arrays.asList(urls));
    }

    public SmartUrlClassLoader(URL[] urls) {
        this(urls, ClassLoader.getSystemClassLoader());
    }

    public SmartUrlClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
        this(urls, parent);
        // 在JDK 17中，URLStreamHandlerFactory参数不再需要特殊处理
    }

    protected void addURL(URL url) {
        if (!urls.contains(url)) {
            urls.add(url);
        }
    }

    public URL[] getURLs() {
        return urls.toArray(new URL[0]);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = name.replace('.', '/').concat(".class");
        for (URL url : urls) {
            try {
                byte[] classData = getClassData(url, path);
                if (classData != null) {
                    CodeSource cs = new CodeSource(url, (CodeSigner[]) null);
                    return defineClass(name, classData, 0, classData.length, cs);
                }
            } catch (Exception e) {
                // 继续尝试下一个URL
            }
        }
        throw new ClassNotFoundException(name);
    }

    private byte[] getClassData(URL url, String path) throws IOException {
        if ("file".equals(url.getProtocol())) {
            try {
                String filePath = URLDecoder.decode(url.getPath(), "UTF-8");
                Path classPath = Paths.get(filePath, path);
                if (Files.exists(classPath)) {
                    return Files.readAllBytes(classPath);
                }
            } catch (Exception e) {
                // 忽略异常，尝试其他方法
            }
        } else if ("jar".equals(url.getProtocol())) {
            try {
                String jarPath = url.getPath();
                int separatorIndex = jarPath.indexOf("!/");
                if (separatorIndex != -1) {
                    String jarFile = jarPath.substring(0, separatorIndex);
                    String entryPath = jarPath.substring(separatorIndex + 2);

                    if (jarFile.startsWith("file:")) {
                        jarFile = jarFile.substring(5);
                    }

                    jarFile = URLDecoder.decode(jarFile, "UTF-8");
                    entryPath = path;

                    try (JarFile jar = new JarFile(jarFile)) {
                        JarEntry entry = jar.getJarEntry(entryPath);
                        if (entry != null) {
                            try (InputStream is = jar.getInputStream(entry)) {
                                return is.readAllBytes();
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // 忽略异常，继续尝试其他URL
            }
        }
        return null;
    }

    protected Package definePackage(String name, Manifest man, URL url) throws IllegalArgumentException {
        String path = name.replace('.', '/').concat("/");
        String specTitle = null, specVersion = null, specVendor = null;
        String implTitle = null, implVersion = null, implVendor = null;
        String sealed = null;

        if (man != null) {
            Attributes attributes = man.getAttributes(path);
            if (attributes != null) {
                specTitle = attributes.getValue(Attributes.Name.SPECIFICATION_TITLE);
                specVersion = attributes.getValue(Attributes.Name.SPECIFICATION_VERSION);
                specVendor = attributes.getValue(Attributes.Name.SPECIFICATION_VENDOR);
                implTitle = attributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
                implVersion = attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
                implVendor = attributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
                sealed = attributes.getValue(Attributes.Name.SEALED);
            }

            Attributes mainAttributes = man.getMainAttributes();
            if (mainAttributes != null) {
                if (specTitle == null) {
                    specTitle = mainAttributes.getValue(Attributes.Name.SPECIFICATION_TITLE);
                }
                if (specVersion == null) {
                    specVersion = mainAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION);
                }
                if (specVendor == null) {
                    specVendor = mainAttributes.getValue(Attributes.Name.SPECIFICATION_VENDOR);
                }
                if (implTitle == null) {
                    implTitle = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
                }
                if (implVersion == null) {
                    implVersion = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
                }
                if (implVendor == null) {
                    implVendor = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
                }
                if (sealed == null) {
                    sealed = mainAttributes.getValue(Attributes.Name.SEALED);
                }
            }
        }

        URL sealBase = "true".equalsIgnoreCase(sealed) ? url : null;
        return definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase);
    }

    @Override
    public URL getResource(String name) {
        // 首先尝试在URL列表中查找资源
        URL url = findResource(name);
        if (url == null) {
            // 如果没找到，委托给父类加载器
            if (getParent() != null) {
                url = getParent().getResource(name);
            } else {
                // 如果没有父类加载器，使用系统类加载器
                url = ClassLoader.getSystemClassLoader().getResource(name);
            }
        }
        return url;
    }

    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        Vector<URL> resources = new Vector<>();
        // 查找当前类加载器中的资源
        URL url = findResource(name);
        if (url != null) {
            resources.add(url);
        }

        // 获取父类加载器中的资源
        if (getParent() != null) {
            Enumeration<URL> parentResources = getParent().getResources(name);
            while (parentResources.hasMoreElements()) {
                resources.add(parentResources.nextElement());
            }
        } else {
            // 如果没有父类加载器，使用系统类加载器
            Enumeration<URL> systemResources = ClassLoader.getSystemClassLoader().getResources(name);
            while (systemResources.hasMoreElements()) {
                resources.add(systemResources.nextElement());
            }
        }
        return resources.elements();
    }

    @Override
    public URL findResource(String name) {
        for (URL url : urls) {
            try {
                URL resourceURL = getResourceURL(url, name);
                if (resourceURL != null) {
                    return resourceURL;
                }
            } catch (Exception e) {
            }
        }
        return null;
    }

    private URL getResourceURL(URL baseUrl, String name) throws IOException {
        if ("file".equals(baseUrl.getProtocol())) {
            try {
                String basePath = URLDecoder.decode(baseUrl.getPath(), "UTF-8");
                Path resourcePath = Paths.get(basePath, name);
                if (Files.exists(resourcePath)) {
                    return resourcePath.toUri().toURL();
                }
            } catch (Exception e) {
            }
        } else if ("jar".equals(baseUrl.getProtocol())) {
            String jarPath = baseUrl.getPath();
            int separatorIndex = jarPath.indexOf("!/");
            if (separatorIndex != -1) {
                String jarFile = jarPath.substring(0, separatorIndex);
                if (jarFile.startsWith("file:")) {
                    jarFile = jarFile.substring(5);
                }
                jarFile = URLDecoder.decode(jarFile, "UTF-8");

                try (JarFile jar = new JarFile(jarFile)) {
                    JarEntry entry = jar.getJarEntry(name);
                    if (entry != null) {
                        return new URL("jar:" + baseUrl + "!/" + name);
                    }
                }
            }
        }
        return null;
    }

    @Override
    public Enumeration<URL> findResources(String name) throws IOException {
        Vector<URL> resources = new Vector<>();
        for (URL url : urls) {
            try {
                URL resourceURL = getResourceURL(url, name);
                if (resourceURL != null) {
                    resources.add(resourceURL);
                }
            } catch (Exception e) {
                // 继续尝试下一个URL
            }
        }
        return resources.elements();
    }

    protected PermissionCollection getPermissions(CodeSource codesource) {
        PermissionCollection perms = super.getPermissions(codesource);

        URL url = codesource.getLocation();
        if (url != null) {
            try {
                Permission permission;
                if ("file".equals(url.getProtocol())) {
                    String path = URLDecoder.decode(url.getPath(), "UTF-8");
                    if (path.endsWith(".jar") || path.endsWith("/")) {
                        permission = new FilePermission(path, "read");
                    } else {
                        permission = new FilePermission(path + "-", "read");
                    }
                } else {
                    String host = url.getHost();
                    if (host == null || host.isEmpty()) {
                        host = "localhost";
                    }
                    permission = new SocketPermission(host, "connect,accept");
                }
                perms.add(permission);
            } catch (Exception e) {
                // 忽略权限添加异常
            }
        }

        return perms;
    }

    public static SmartUrlClassLoader newInstance(final URL[] urls, final ClassLoader parent) {
        return new SmartUrlClassLoader(urls, parent);
    }

    public static SmartUrlClassLoader newInstance(final URL[] urls) {
        return new SmartUrlClassLoader(urls);
    }
}
